package ircbot;

import org.jibble.pircbot.*;

import java.util.Vector;
import java.util.Iterator;
import util.Observable;
import util.Observer;
import util.TreeNode;

/**
 * This is the class that handles all the jobs and gets events.
 *
 * @author andreas
 */
public class Bot extends PircBot implements Observable {
    private Vector<JobInitializer> jobInitializers = new Vector<JobInitializer>();
    private Log log = new Log();
    
    /**
     * Constructor
     */
    public Bot(String name) {
        setName(name);
    }

    /**
     * adds a job initializer, so that a new kind of job can be initialized
     * @param jobInit
     */
    public void addJobInitializer(JobInitializer jobInit) {
        jobInitializers.add(jobInit);
        notifyObservers();
    }

    public void setLog(Log log) {
        this.log = log;
    }

    public Log getLog() {
        return log;
    }

    public TreeNode<String> getJobs() {
        TreeNode<String> root = new TreeNode<String>("Jobs");

        for(Iterator<JobInitializer> i = jobInitializers.iterator(); i.hasNext();) {
            JobInitializer initer = i.next();

            TreeNode<String> child = new TreeNode<String>(initer.getCmd());
            root.addChild(child);

            for(Iterator<Job> j = initer.iterator(); j.hasNext();) {
                //not giving the jobs an apropiet name yet.
                Job job = j.next();
                TreeNode<String> childChild = new TreeNode<String>(initer.getCmd());
                child.addChild(childChild);
            }
        }

        return root;
    }

    public void executeCommand(String command, String channel) {
        handleInput(command, channel);
    }

    /**
     * Helper method for handling input not directed to jobs, but to the bot itself
     *
     * @param command
     * @param channel
     */
    private void handleInput(String command, String channel) {
        //i substring to remove the $ sign
        String[] subCommand = command.substring(1).split(" ");


        if(subCommand[0].equalsIgnoreCase("start") && subCommand.length == 3) {
            for(Iterator<JobInitializer> i = jobInitializers.iterator(); i.hasNext();) {
                JobInitializer jobIniter = i.next();

                if(subCommand[1].equalsIgnoreCase(jobIniter.getCmd())) {
                    jobIniter.newJob(channel.toLowerCase() + "." + subCommand[2], this, channel);
                    notifyObservers();
                }
            }
        } else if(subCommand[0].equalsIgnoreCase("stop") && subCommand.length == 3) {
            for(Iterator<JobInitializer> i = jobInitializers.iterator(); i.hasNext();) {
                JobInitializer jobIniter = i.next();

                if(subCommand[1].equalsIgnoreCase(jobIniter.getCmd())) {
                    jobIniter.removeJob(channel.toLowerCase() + "." + subCommand[2]);
                    notifyObservers();
                }
            }
        } else if(subCommand[0].equalsIgnoreCase("join") && subCommand.length == 2) {
            joinChannel(subCommand[1]);
            notifyObservers();
        } else if(subCommand[0].equalsIgnoreCase("part") && subCommand.length == 1) {
            partChannel(channel);
            notifyObservers();
        } else if(subCommand[0].equalsIgnoreCase("showChannels") && subCommand.length == 1) {
            String[] channels = getChannels();
            sendMessage(channel, "These are the channels i am connected to");
            for(int i = 0; i < channels.length; i++) {
                sendMessage(channel, "  -" + channels[i]);
            }
        } else if(subCommand[0].equalsIgnoreCase("showJobs") && subCommand.length == 1) {
            sendMessage(channel, "These are the jobs i have running");

            //TDO Deapth first traversal of getJobs here could probably be done beter
            TreeNode<String> root = getJobs();
            for(Iterator<TreeNode<String>> i = root.getChildren(); i.hasNext();) {
                TreeNode<String> child = i.next();
                sendMessage(channel, "-"+child.getValue());
                for(Iterator<TreeNode<String>> j = child.getChildren(); j.hasNext();) {
                    TreeNode<String> childChild = j.next();
                    sendMessage(channel, " -"+childChild.getValue());
                }
            }
        }

    }

    /**
     * Ovverides the log file, so it notifyes observers. Many things done by the bot
     * Causes this function to be called, so this will effectively notify observes
     * about many things.. it is kind of an hack though.
     * @param line
     */
    @Override
    public void log(String line) {
        log.appendLine(line);
        notifyObservers();
    }

    /**
     * This method gets called every time a message is recived.
     * We chekt if it is a command that tell us to created a new job. If it is we do
     * if it isent we just pass the message to all jobs
     *
     * @param channel
     * @param sender
     * @param login
     * @param hostname
     * @param message
     */
    @Override
    protected void onMessage(String channel, String sender, String login, String hostname, String message) {
        //this event is special, because we need to check if the message sent was a command
        // TODO the $ sign shouldent be hardcoded. Could be loaded from some kind of conf. file
        if(message.startsWith("$")) {
            handleInput(message, channel);
        } else {
            for(Iterator<JobInitializer> i = jobInitializers.iterator(); i.hasNext();) {
                for(Iterator<Job> j = i.next().iterator(); j.hasNext();) {
                    Job job = j.next();
                    job.onMessage(channel, sender, login, hostname, message);
                }
            }
        }

        afterEachEvent();
    }

    @Override
    protected void onJoin(String channel, String sender, String login, String hostname)  {
        for(Iterator<JobInitializer> i = jobInitializers.iterator(); i.hasNext();) {
            for(Iterator<Job> j = i.next().iterator(); j.hasNext();) {
                Job job = j.next();
                job.onJoin(channel, sender, login, hostname);
            }
        }
    }

    /**
     * Private helper class for things that should happen after each event.
     * This function is a little like a heartbeat for the bot
     */
    private void afterEachEvent() {
        //Nothing here yet
    }

    //////////////////--------------
    //OBSERVER STUFF//-------------------------------
    //////////////////---------------------------------------------

    Vector<Observer> observers = new Vector<Observer>();

    public void addObserver(Observer observer) {
        if(observer != null) {
            observers.add(observer);
        }
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    private void notifyObservers() {
        for(Iterator<Observer> i = observers.iterator(); i.hasNext();) {
            Observer o = i.next();

            o.update();
        }
    }
}
